#!/usr/bin/env python
#coding: utf-8

from twisted.application import internet
from twisted.application import service
from twisted.internet import defer
from twisted.internet import protocol 
from twisted.internet import reactor
from twisted.internet import threads
from twisted.web import http
from twisted.web import proxy
from twisted.web import resource
from twisted.web import script
from twisted.web import server
from twisted.web import static
import os
import urllib
import urllib2
import urlparse
import zlib


respond_html = '''<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">

<head>
<meta http-equiv="Content-Language" content="zh-cn" />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>GappProxy</title>
<style type="text/css">
.respond {
    font-size: small;
}
.server {
    font-size: medium;
}
</style>
</head>
<body>
<p><span class="respond">Server Respond Code: %d </span></p>
<p><span class="respond">%s</span></p>
<hr />
<span class="server">GappProxy</span>
</body>
</html>
'''

class ProxyError( Exception ):
    def __init__( self, value ):
        self.value = value
    def __str__( self ):
        return repr( self.value )

class ConfigError( Exception ):
    def __init__( self, value ):
        self.value = value
    def __str__( self ):
        return repr( self.value )
    
class GappProxyRequest( http.Request ):
    
    protocols = {'http': proxy.ProxyClientFactory}
    ports = {'http': 80}

    def __init__( self, channel, queued, reactor=reactor ):
        http.Request.__init__( self, channel, queued )
        self.reactor = reactor
        self.max_post_data_length = 100000
        self.post_data_length = 0
        self.post_data = ''

    def close( self, code, respond_str ):
        '''
        close func, always add return when called close
        @param code: http code
        @param respond_str: http respond string
        '''
        self.write( respond_html % ( code, respond_str ) )
        self.setResponseCode( code, '' )
        self.finish()
        
    def _get_content_from_fetcher( self, r ):
        '''
        send browser http request to google app engine fetcher, wait all data received
        @param r: request
        '''
        _r = r
        # fixed win32 error , because urllib2.urlopen default use IE proxy setting
        _request = urllib2.Request( GOOGLE_APP_ENGINE_SITE )
        _request.add_header( 'Accept-Encoding', 'identity, *;q=0' )
        _request.add_header( 'Connection', 'close' )
        # create new opener
        _proxy_handler = urllib2.ProxyHandler( {} )
        _opener = urllib2.build_opener( _proxy_handler )
        # set the opener as the default opener
        urllib2.install_opener( _opener )
        
        try:
            _from_fetch_respond = urllib2.urlopen( _request, _r )
        except urllib2.HTTPError:
            raise ProxyError, 'Remote google app enging fetcher error'
        
        # parse google app engine fetchd respond
        _text_content = True
        # for status line
        _line = _from_fetch_respond.readline().strip()
        respond_code = int( _line.split()[1] )
        
        # try read http head
        respond_head = {}
        while True:
            _line = _from_fetch_respond.readline()
            _line = _line.strip().lower()
            if _line == '':
                break
            try:
                n, k = _line.split( ':', 1 )
                respond_head[n.strip()] = k.strip()
            except :
                pass

        if respond_head.has_key( 'content-type' ):
            if respond_head['content-type'].startswith( 'text' ):
                _text_content = True
            else:
                _text_content = False
        
        # read page data
        if _text_content:
            try:
                respond_content = zlib.decompress( _from_fetch_respond.read() )
            except zlib.error:
                raise ProxyError, 'Unzip respond data error'
        else:
            respond_content = _from_fetch_respond.read()

        return ( respond_code, respond_head, respond_content )
    
    def send_error_to_browser( self, e ):
        self.close( 500, e )
        return
    
    def send_respond_to_browser( self, *argv ):
        '''
        send all google app engine respons content to browser
        @param argv: (respond_code, respond_head, respond_content)
        '''
        callback_resp = argv[0]
        self.setResponseCode( callback_resp[0], '' )
        for k, v in callback_resp[1].iteritems():
            self.setHeader( k, v )
        self.write( callback_resp[2] )
        self.finish()

    def get_respond_from_fetcher( self, r ):    
        self.d = threads.deferToThread( self._get_content_from_fetcher, r )
        return self.d
        
    def process( self ):
        #print self.channel.factory.test
        #check http method , because i can not process CONNECT method
        if self.method == 'GET' or self.method == 'HEAD' or self.method =='POST': 
            # try to get http header Content-Length
            if self.getAllHeaders().has_key( 'Content-Length' ):
                self.post_data_length = int( self.getHeader( 'Content-Length' ) )

            # over max post data length?
            if self.post_data_length > self.max_post_data_length:
                #how to return http error code ?
                self.close( 400, 'over max post data length' )
                return 
            
            if self.post_data_length > 0:
                self.post_data = self.content.read( self.post_data_length )
                if len( self.post_data ) != self.post_data_length:
                    self.close( 400, 'post data length not equal header Content-Length' )
                    return

            self.r_scheme , self.r_netloc, self.r_path, self.r_params, self.r_query, self.r_frag = urlparse.urlparse( self.uri )
            
            #check request protocol
            if self.r_scheme != 'http' or not self.r_netloc:
                self.close( 400, 'request is not valid url' )
                return
            
            self._new_path = urlparse.urlunparse( ( self.r_scheme , self.r_netloc, self.r_path, self.r_params, self.r_query, '' ) )
            
            self._new_params =urllib.urlencode( {'method': self.method, \
                                             'path':self._new_path, \
                                             'headers': self.getAllHeaders(), \
                                             'encodeResponse': 'compress', \
                                             'postdata': self.post_data
                                             } )
            
            self.my_defer = self.get_respond_from_fetcher( self._new_params )
            self.my_defer.addErrback( self.send_error_to_browser )
            self.my_defer.addCallback( self.send_respond_to_browser )
            
        else:
            self.close( 400, 'BAD REQUEST, not support %s method' % self.method )
            return
        
class GappProxyProtocol( http.HTTPChannel ):
    '''
    Proxy Protocol
    '''
    requestFactory = GappProxyRequest

#Config Proxy protocol, just use http.protocol
#blah blah
    
class ProxyServices( service.Service ):
    
    def __init__(self):
        '''
        keep global parmas at here, Proxy Factory and Config Factory can be use it
        '''
        print 'start Server'
        self.test = "----------------"
    
    def getProxyFactroy( self ):
        proxy_factory = http.HTTPFactory( logPath=os.path.join( os.getcwd(), 'proxy_access.log' ) )
        proxy_factory.protocol = GappProxyProtocol
        
        #set global service
        proxy_factory.test = self.test
        
        return proxy_factory
    
    def getConfigFactory( self ):
        admin_site_root = static.File( os.path.join( os.getcwd(), 'htdocs' ) )
        if not DEBUG: admin_site_root.indexNames = ( 'index.rpy', )
        admin_site_root.ignoreExt( ".rpy" )
        admin_site_root.processors = {'.rpy': script.ResourceScript}
        config_factory = server.Site( admin_site_root , logPath=os.path.join( os.getcwd(), 'access.log' ) )
        
        #set global service
        config_factory.test = self.test
                
        return config_factory
    

#===============================================================================
# do some thing befor listening
#===============================================================================
global GOOGLE_APP_ENGINE_SITE
global LOCAL_PROXY_HOST
global LOCAL_PROXY_PORT
global LOCAL_CONFIG_HOST
global LOCAL_CONFIG_PORT
global DEBUG

GOOGLE_APP_ENGINE_SITE = 'http://snote.appspot.com/fetch.py'
#LOCAL_PROXY_HOST = ''
LOCAL_PROXY_HOST = '127.0.0.1'
LOCAL_PROXY_PORT = 3128
#LOCAL_CONFIG_HOST = ''
LOCAL_CONFIG_HOST = '127.0.0.1'
LOCAL_CONFIG_PORT = 8000
DEBUG = True
    
application = service.Application( 'GappProxy' )
s = ProxyServices()
serviceCollection = service.IServiceCollection( application )

p_factory = s.getProxyFactroy()
c_factory = s.getConfigFactory() 

#bind ip address and port, should I use internet.TCPServer()? need twisted.py to run server
reactor.listenTCP( interface=LOCAL_PROXY_HOST, port=LOCAL_PROXY_PORT , factory=p_factory ) #@UndefinedVariable
reactor.listenTCP( interface=LOCAL_CONFIG_HOST, port=LOCAL_CONFIG_PORT , factory=c_factory ) #@UndefinedVariable

# now run reactor  
reactor.run() #@UndefinedVariable
    